﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace XNAViewerSample
{
    /// <summary>
    /// Class structure for morph information
    /// </summary>
    public class MorphInfo
    {
        #region Fields
        private string morphNameVal;
        private List<List<int>> morphIndexVal;
        private List<List<float>> morphVertexVal;
        private MorphPlayer morphPlayer;
        #endregion

        /// <summary>
        /// Constructs a new morph information object
        /// </summary>
        public MorphInfo(string name)
        {
            // Initialize morph structure
            morphIndexVal = new List<List<int>>();
            morphIndexVal.Clear();
            morphVertexVal = new List<List<float>>();
            morphVertexVal.Clear();
            // Initialize player
            morphPlayer = new MorphPlayer();
            // Set morph name
            morphNameVal = name;
        }

        /// <summary>
        /// Public method to access ID of corresponding morphInfo class
        /// </summary>
        public string MorphName
        {
            get { return morphNameVal; }
        }

        /// <summary>
        /// Public method to access index info
        /// </summary>
        public List<List<int>> IndexList
        {
            get { return morphIndexVal; }
        }

        /// <summary>
        /// Public method to access vertex info
        /// </summary>
        public List<List<float>> VertexList
        {
            get { return morphVertexVal; }
        }

        /// <summary>
        /// Public method to set vertex data
        /// </summary>
        /// <param name="value">Vertex data of type float</param>
        public void Add(List<float> value)
        {
            morphVertexVal.Add(value);
        }

        /// <summary>
        /// Public method to set index data
        /// </summary>
        /// <param name="value">Vertex indices</param>
        public void Add(List<int> value)
        {
            morphIndexVal.Add(value);
        }

        /// <summary>
        /// Public method to access morph player
        /// </summary>
        public MorphPlayer Player
        {
            get { return morphPlayer; }
        }


    }

    /// <summary>
    /// Class structure for morph player
    /// </summary>
    public class MorphPlayer
    {
        #region Fields
        private int fps;
        private TimeSpan fTime;
        private TimeSpan bTime;
        private TimeSpan dTime;
        private TimeSpan rTime;
        private float weight;
        private state pState;

        //public bool isActive;
        public enum state { INACTIVE, FORWARD, HOLD, BACKWARD, IDLE };

        // params for the player
        private TimeSpan playTime;
        //private bool fromForwardState;
        //private bool fromBackwardState;
        private state prevState;
        #endregion

        /// <summary>
        /// Constructs a new morph player object
        /// </summary>
        public MorphPlayer()
        {
            fps = 30;
            fTime = TimeSpan.Zero;
            bTime = TimeSpan.Zero;
            dTime = TimeSpan.MaxValue;
            rTime = TimeSpan.Zero;
            weight = 1.0f;

            pState = state.INACTIVE;
            prevState = state.INACTIVE;
            playTime = TimeSpan.Zero;
        }

        /// <summary>
        /// Public method to access frames per second
        /// Don't know if this is needed. Default is 30 fps.
        /// </summary>
        public int FPS
        {
            get { return fps; }
            set { fps = value; }
        }

        /// <summary>
        /// Public method to access forward blend time
        /// Forward blend time is transition time from base to morph. Default is 0.
        /// </summary>
        public TimeSpan ForwardTime
        {
            get { return fTime; }
            set { fTime = value; }
        }

        /// <summary>
        /// Public method to access backward blend time.
        /// Backward blend time is transition time from morph to base. Default is 0.
        /// </summary>
        public TimeSpan BackwardTime
        {
            get { return bTime; }
            set { bTime = value; }
        }

        /// <summary>
        /// Public method to access morph play duration time.
        /// Duration time is hold time of morph play. Default is max time.
        /// </summary>
        public TimeSpan Duration
        {
            get { return dTime; }
            set { dTime = value; }
        }

        /// <summary>
        /// Public method to access morph weight value.
        /// Weight value refers to difference between base and morph mesh. Default is 1.0.
        /// </summary>
        public float Weight
        {
            get { return weight; }
            set { weight = value; }
        }

        /// <summary>
        /// Public method to access repeat play time.
        /// If > 0, play will repeat after expiration of timer. Default is 0.
        /// </summary>
        public TimeSpan RepeatTime
        {
            get { return rTime; }
            set { rTime = value; }
        }

        /// <summary>
        /// Public method to start player.
        /// </summary>
        /// <returns></returns>
        public void Start()
        {
            if (fTime > TimeSpan.Zero)
                pState = state.FORWARD;
            else
                pState = state.HOLD;
            // initialize timer
            playTime = TimeSpan.Zero;
        }

        /// <summary>
        /// Public method to stop player.
        /// </summary>
        public void Stop()
        {
            pState = state.INACTIVE;
            playTime = TimeSpan.Zero;
        }

        /// <summary>
        /// Public method to access play state.
        /// </summary>
        public state State
        {
            get { return pState; }
        }

        /// <summary>
        /// Public method that updates morph animation based on time
        /// </summary>
        /// <param name="time">Elapsed time</param>
        /// <returns>Computed morph weight</returns>
        public float Update(TimeSpan time)
        {
            float compWeight = 0.0f ;
            playTime += time;

            // update state table
            state nextState = pState;   // assume hold current state
            switch (pState)
            {
                case state.FORWARD:
                    compWeight = (float)time.Milliseconds / (float)fTime.Milliseconds;
                    //compWeight = (compWeight > 1.0f) ? 1.0f : compWeight;
                    if (playTime > fTime)
                    {
                        if (dTime > TimeSpan.Zero)
                            nextState = state.HOLD;
                        else if (bTime > TimeSpan.Zero)
                            nextState = state.BACKWARD;
                        else if (rTime > TimeSpan.Zero)
                            nextState = state.IDLE;
                        else
                            nextState = state.INACTIVE;
                        playTime = TimeSpan.Zero;
                    }
                    break;
                case state.HOLD:
                    if ((prevState == state.INACTIVE) ||
                        (prevState == state.IDLE))
                        compWeight = 1.0f;
                    else
                        compWeight = 0.0f;              // perform a morph transform one-time

                    if (playTime > dTime)
                    {
                        if (bTime > TimeSpan.Zero)
                            nextState = state.BACKWARD;
                        else if (rTime > TimeSpan.Zero)
                            nextState = state.IDLE;
                        else
                            nextState = state.INACTIVE;
                        playTime = TimeSpan.Zero;
                    }
                    break;
                case state.BACKWARD:
                    compWeight = -1.0f * (float)time.Milliseconds / (float)bTime.Milliseconds;
                    //compWeight = (compWeight > 0.0f) ? 0.0f : compWeight; 
                    if (playTime > bTime)
                    {
                        if (rTime > TimeSpan.Zero)
                            nextState = state.IDLE;
                        else
                            nextState = state.INACTIVE;
                        playTime = TimeSpan.Zero;
                    }
                    break;
                case state.IDLE:
                    if (prevState == state.HOLD)
                        compWeight = -1.0f;
                    else
                        compWeight = 0.0f;

                    if (playTime > rTime)
                    {
                        if (fTime > TimeSpan.Zero)
                            nextState = state.FORWARD;
                        else if (dTime > TimeSpan.Zero)
                            nextState = state.HOLD;
                        else
                            nextState = state.INACTIVE;
                        playTime = TimeSpan.Zero;
                    }
                    break;
                case state.INACTIVE:
                default:
                    nextState = pState;
                    break;
            }

            prevState = pState;
            pState = nextState;
            return (float)compWeight * (float)weight;   //scale accdg to weight Field
        }

    }

    /// <summary>
    /// Creates a loader for morph targets
    /// </summary>
    public class MorphTarget
    {
        #region Fields
        private IDictionary<String, MorphInfo> MorphVal;
        private Model baseModel;

        private const float delta = 0.000001f;
        #endregion

        /// <summary>
        /// Constructs an empty morph object
        /// </summary>
        public MorphTarget()
        {
            MorphVal = new Dictionary<String, MorphInfo>();
        }

        /// <summary>
        /// Constructs an empty morph object
        /// </summary>
        /// <param name="baseMesh">Sets base mesh object to baseMesh</param>
        public MorphTarget(Model baseMesh)
        {
            MorphVal = new Dictionary<String, MorphInfo>();
            SetBaseMesh(baseMesh);
        }

        /// <summary>
        /// Public method to access morph information
        /// </summary>
        public IDictionary<String, MorphInfo> Morphs
        {
            get { return MorphVal; }
        }

        /// <summary>
        /// Sets the base mesh for the morph targets, always at index=0 and name=Base
        /// </summary>
        /// <param name="baseMesh">The Model instance of the base mesh</param>
        public bool SetBaseMesh(Model baseMesh)
        {
            baseModel = baseMesh;
            return CreateMorphTarget(baseMesh, "Base");
        }

        /// <summary>
        /// Adds a morph target from morphMesh
        /// </summary>
        /// <param name="morphMesh">Mesh from where the morph info will be extracted</param>
        /// <param name="morphName">String identifier for morph</param>
        /// <returns>True if successful</returns>
        public bool Add(Model morphMesh, String morphName)
        {
            return CreateMorphTarget(morphMesh, morphName);
        }

        /// <summary>
        /// Creates a morph target from morphMesh
        /// TODO: sparse morph optimization, diff and weighting
        /// </summary>
        /// <param name="morphMesh">Mesh from where the morph info will be extracted</param>
        /// <param name="morphName">String identifier for morph</param>
        /// <returns>True if successful</returns>
        private bool CreateMorphTarget(Model morphMesh, String morphName)
        {
            MorphInfo info = new MorphInfo(morphName);
            int i=0;
            foreach (ModelMesh mesh in morphMesh.Meshes)
            {
                List<int> intList = new List<int>();
                List<float> flList = new List<float>();
               
                // assume consistent stride for all mesh parts
                //int strideBytes = mesh.MeshParts[i].VertexDeclaration.GetVertexStrideSize(0);
                //int stride = strideBytes / sizeof(float);
                int strideBytes = mesh.MeshParts[i].VertexBuffer.VertexDeclaration.VertexStride;
                int stride = strideBytes / sizeof(float);
                int numVertices = mesh.MeshParts[i].VertexBuffer.VertexCount;//  .SizeInBytes / strideBytes;

                float[] flArray = new float[numVertices * stride];
                mesh.MeshParts[i].VertexBuffer.GetData<float>(flArray);

                if (morphName == "Base")
                {
                    // these are base vertices, just add to MorphInfo
                    info.Add(intList);
                    info.Add(flArray.ToList<float>());
                }
                else
                {
                    // requires a Base mesh
                    if (MorphVal.ContainsKey("Base") == false)
                    {
                        throw new Exception("Morph targets require a base mesh.");
                    }

                    // check if consistent with base mesh
                    if (MorphVal["Base"].VertexList[i].Count == flArray.Length)
                    {
                        // form a sparse mask array
                        for (int j = 0; j < flArray.Length; j++)
                        {
                            float diff = flArray[j] - MorphVal["Base"].VertexList[i][j];
                            if (diff * diff > delta)
                            {
                                intList.Add(j);
                                //flList.Add(flArray[j]);
                                flList.Add(diff);
                            }
                        }
                        info.Add(intList);  // add index list
                        info.Add(flList);   // add vertex list
                    }
                    else
                        return false;
                }
                i++;
            }

            // Add to morph dictionary
            MorphVal.Add(morphName, info);
            return true;
        }

        /// <summary>
        /// Sets active morph to morphName
        /// TODO: blending, weighting
        /// </summary>
        /// <param name="morphName">Name of morph</param>
        private void SetMorph(String morphName, float weight)
        {
            int i = 0;
            foreach (ModelMesh mesh in baseModel.Meshes)
            {
                if (morphName == "Base")
                {
                    mesh.MeshParts[i].VertexBuffer.SetData<float>(MorphVal["Base"].VertexList[i].ToArray());
                }
                else
                {
                    int len = MorphVal["Base"].VertexList[i].Count;    // or recompute len
                    float[] baseMorph = new float[len];

                    mesh.MeshParts[i].VertexBuffer.GetData<float>(baseMorph);
                    // modify baseMorph to apply morph info
                    for (int j = 0; j < MorphVal[morphName].IndexList[i].Count; j++)
                    {
                        baseMorph[MorphVal[morphName].IndexList[i][j]] +=
                            weight * MorphVal[morphName].VertexList[i][j];
                        //baseMorph[MorphVal[morphName].IndexList[i][j]] =
                        //    MorphVal["Base"].VertexList[i][j] +
                        //    weight * MorphVal[morphName].VertexList[i][j];
                    }
                    mesh.MeshParts[i].VertexBuffer.SetData<float>(baseMorph);
                }
                i++;
            }
        }

        /// <summary>
        /// Resets morph to Base mesh
        /// </summary>
        public void Reset()
        {
            /*
            int i = 0;
            foreach (ModelMesh mesh in baseModel.Meshes)
            {
                mesh.VertexBuffer.SetData<float>(MorphVal["Base"].VertexList[i++].ToArray());
            }*/
            SetMorph("Base", 1.0f);
        }

        /// <summary>
        /// Update morphs based on time elapsed
        /// </summary>
        /// <param name="elapsed">Elapsed game time</param>
        public void Update(TimeSpan elapsed)
        {
            float wt = 0.0f;
            foreach (MorphInfo mi in MorphVal.Values)
            {
                if (mi.Player.State != MorphPlayer.state.INACTIVE)
                {
                    wt = mi.Player.Update(elapsed);
                    if (wt != 0)
                        SetMorph(mi.MorphName, wt);
                }
            }
        }
    }
}
